package cz.data.common.database;

import com.zaxxer.hikari.HikariDataSource;
import cz.data.common.database.exception.DataQueryException;
import cz.data.common.support.db.DbType;
import cz.data.common.support.db.DbProperty;
import cz.data.common.utils.ThrowableUtil;
import org.apache.commons.lang3.StringUtils;

import javax.sql.DataSource;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CacheDataSourceBean {

    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    /**
     * 数据源缓存
     */
    private static final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();

    /**
     * hook DatabaseAutoConfiguration.destroy()
     */
    public static void shutdown() {
        for (Map.Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
            DataSource dataSource = entry.getValue();
            if (dataSource instanceof AutoCloseable) {
                AutoCloseable closeable = (AutoCloseable) dataSource;
                ThrowableUtil.throwable(closeable::close);
            }
        }
    }

    public static void invalid(String id) {
        DataSource dataSource = dataSourceMap.remove(id);
        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
    }

    public static DataSource createDataSource(DbProperty property) {
        return createDataSource(property.getId(), property.getDbType(), property.getHost(), property.getPort(),
                property.getUsername(), property.getPassword(), property.getDbName(),
                property.getSid(), property.getMaxConn());
    }

    public static DataSource createDataSource(String id, String dbType, String host, int port,
                                              String username, String password, String dbName,
                                              String sid, int maxConn) {
        DataSource dataSource;
        if (StringUtils.isNotBlank(id)) {
            dataSource = dataSourceMap.get(id);
            if (null == dataSource) {
                lock.writeLock().lock();
                try {
                    dataSource = dataSourceMap.get(id);
                    if (null == dataSource) {
                        HikariDataSource hikari = new HikariDataSource();
                        hikari.setJdbcUrl(trainToJdbcUrl(dbType, host, port, dbName, sid));
                        hikari.setUsername(username);
                        hikari.setPassword(password);
                        hikari.setMinimumIdle(1);
                        hikari.setMaximumPoolSize(maxConn);
                        hikari.setConnectionTimeout(5000);
                        hikari.setIdleTimeout(Duration.ofMinutes(5).toMillis());
                        dataSourceMap.put(id, hikari);
                        dataSource = hikari;
                    }
                } finally {
                    lock.writeLock().unlock();
                }
            }
        } else {
            HikariDataSource hikari = new HikariDataSource();
            hikari.setJdbcUrl(trainToJdbcUrl(dbType, host, port, dbName, sid));
            hikari.setUsername(username);
            hikari.setPassword(password);
            hikari.setMaximumPoolSize(1);
            hikari.setConnectionTimeout(5000);
            dataSource = hikari;
        }
        return dataSource;
    }

    public static String trainToJdbcUrl(String dbType, String host, int port, String dbName, String sid) {
        String url = DbType.getDbType(dbType).getUrl();
        if (StringUtils.isEmpty(url)) {
            throw new DataQueryException("无效数据库类型!");
        }
        url = url.replace("${host}", host);
        url = url.replace("${port}", String.valueOf(port));
        if (DbType.ORACLE.getDb().equals(dbType) || DbType.ORACLE_12C.getDb().equals(dbType)) {
            url = url.replace("${sid}", sid);
        } else {
            url = url.replace("${dbName}", dbName);
        }
        return url;
    }

}
